{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e00f269d",
   "metadata": {},
   "source": [
    "# SemanticChunker\n",
    "\n",
    "텍스트를 의미론적 유사성에 기반하여 분할합니다.\n",
    "\n",
    "**Reference**\n",
    "\n",
    "- [Greg Kamradt의 노트북](https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb)\n",
    "\n",
    "이 방법은 텍스트를 문장 단위로 분할한 후, 3개의 문장씩 그룹화하고, 임베딩 공간에서 유사한 문장들을 병합하는 과정을 거칩니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90b48cce",
   "metadata": {},
   "source": [
    "샘플 텍스트를 로드하고 내용을 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c170dd43",
   "metadata": {},
   "outputs": [],
   "source": [
    "# data/appendix-keywords.txt 파일을 열어서 f라는 파일 객체를 생성합니다.\n",
    "with open(\"./data/appendix-keywords.txt\") as f:\n",
    "    file = f.read()  # 파일의 내용을 읽어서 file 변수에 저장합니다.\n",
    "\n",
    "# 파일으로부터 읽은 내용을 일부 출력합니다.\n",
    "print(file[:350])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1817a14",
   "metadata": {},
   "source": [
    "## SemanticChunker 생성\n",
    "\n",
    "`SemanticChunker`는 LangChain의 실험적 기능 중 하나로, 텍스트를 의미론적으로 유사한 청크로 분할하는 역할을 합니다.\n",
    "\n",
    "이를 통해 텍스트 데이터를 보다 효과적으로 처리하고 분석할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b06b68b4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab33ae70",
   "metadata": {},
   "source": [
    "`SemanticChunker`를 사용하여 텍스트를 의미적으로 관련된 청크로 분할합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "312e3aae",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_experimental.text_splitter import SemanticChunker\n",
    "from langchain_openai.embeddings import OpenAIEmbeddings\n",
    "\n",
    "# OpenAI 임베딩을 사용하여 의미론적 청크 분할기를 초기화합니다.\n",
    "text_splitter = SemanticChunker(OpenAIEmbeddings())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dab515b0",
   "metadata": {},
   "source": [
    "## 텍스트 분할\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0c9b20b",
   "metadata": {},
   "source": [
    "- `text_splitter`를 사용하여 `file` 텍스트를 문서 단위로 분할합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dfb5870d",
   "metadata": {},
   "outputs": [],
   "source": [
    "chunks = text_splitter.split_text(file)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14a777bc",
   "metadata": {},
   "source": [
    "분할된 청크를 확인합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eec69bff",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 분할된 청크 중 첫 번째 청크를 출력합니다.\n",
    "print(chunks[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f03b26b",
   "metadata": {},
   "source": [
    "`create_documents()` 함수를 사용하여 청크를 문서로 변환할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fadaf823",
   "metadata": {},
   "outputs": [],
   "source": [
    "# text_splitter를 사용하여 분할합니다.\n",
    "docs = text_splitter.create_documents([file])\n",
    "print(docs[0].page_content)  # 분할된 문서 중 첫 번째 문서의 내용을 출력합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1633cf8e",
   "metadata": {},
   "source": [
    "## Breakpoints\n",
    "\n",
    "이 chunker는 문장을 \"분리\"할 시점을 결정하여 작동합니다. 이는 두 문장 간의 임베딩 차이를 살펴봄으로써 이루어집니다.\n",
    "\n",
    "그 차이가 특정 임계값을 넘으면 문장이 분리됩니다.\n",
    "\n",
    "- 참고 영상: https://youtu.be/8OJC21T2SL4?si=PzUtNGYJ_KULq3-w&t=2580\n",
    "\n",
    "### Percentile\n",
    "\n",
    "기본적인 분리 방식은 백분위수(`Percentile`) 를 기반으로 합니다.\n",
    "\n",
    "이 방법에서는 문장 간의 모든 차이를 계산한 다음, 지정한 백분위수를 기준으로 분리합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "744bbd95",
   "metadata": {},
   "outputs": [],
   "source": [
    "text_splitter = SemanticChunker(\n",
    "    # OpenAI의 임베딩 모델을 사용하여 시맨틱 청커를 초기화합니다.\n",
    "    OpenAIEmbeddings(),\n",
    "    # 분할 기준점 유형을 백분위수로 설정합니다.\n",
    "    breakpoint_threshold_type=\"percentile\",\n",
    "    breakpoint_threshold_amount=70,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59aa8318",
   "metadata": {},
   "source": [
    "분할된 결과를 확인합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c7b3262",
   "metadata": {},
   "outputs": [],
   "source": [
    "docs = text_splitter.create_documents([file])\n",
    "for i, doc in enumerate(docs[:5]):\n",
    "    print(f\"[Chunk {i}]\", end=\"\\n\\n\")\n",
    "    print(doc.page_content)  # 분할된 문서 중 첫 번째 문서의 내용을 출력합니다.\n",
    "    print(\"===\" * 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07e83f74",
   "metadata": {},
   "source": [
    "`docs`의 길이를 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20c0cbd0",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(docs))  # docs의 길이를 출력합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21c1c9e8",
   "metadata": {},
   "source": [
    "### Standard Deviation\n",
    "\n",
    "이 방법에서는 지정한 `breakpoint_threshold_amount` 표준편차보다 큰 차이가 있는 경우 분할됩니다.\n",
    "\n",
    "- `breakpoint_threshold_type` 매개변수를 \"standard_deviation\"으로 설정하여 청크 분할 기준을 표준편차 기반으로 지정합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16a8d823",
   "metadata": {},
   "outputs": [],
   "source": [
    "text_splitter = SemanticChunker(\n",
    "    # OpenAI의 임베딩 모델을 사용하여 시맨틱 청커를 초기화합니다.\n",
    "    OpenAIEmbeddings(),\n",
    "    # 분할 기준으로 표준 편차를 사용합니다.\n",
    "    breakpoint_threshold_type=\"standard_deviation\",\n",
    "    breakpoint_threshold_amount=1.25,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "690db96c",
   "metadata": {},
   "source": [
    "분할된 결과를 확인합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1764de39",
   "metadata": {},
   "outputs": [],
   "source": [
    "# text_splitter를 사용하여 분할합니다.\n",
    "docs = text_splitter.create_documents([file])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0743d8f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "docs = text_splitter.create_documents([file])\n",
    "for i, doc in enumerate(docs[:5]):\n",
    "    print(f\"[Chunk {i}]\", end=\"\\n\\n\")\n",
    "    print(doc.page_content)  # 분할된 문서 중 첫 번째 문서의 내용을 출력합니다.\n",
    "    print(\"===\" * 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "095170af",
   "metadata": {},
   "source": [
    "`docs`의 길이를 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee9f46ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(docs))  # docs의 길이를 출력합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c5b03d9b",
   "metadata": {},
   "source": [
    "### Interquartile\n",
    "\n",
    "이 방법에서는 사분위수 범위(interquartile range)를 사용하여 청크를 분할합니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb408177",
   "metadata": {},
   "source": [
    "- `breakpoint_threshold_type` 매개변수를 \"interquartile\"로 설정하여 청크 분할 기준을 사분위수 범위로 지정합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f32f5fe8",
   "metadata": {},
   "outputs": [],
   "source": [
    "text_splitter = SemanticChunker(\n",
    "    # OpenAI의 임베딩 모델을 사용하여 의미론적 청크 분할기를 초기화합니다.\n",
    "    OpenAIEmbeddings(),\n",
    "    # 분할 기준점 임계값 유형을 사분위수 범위로 설정합니다.\n",
    "    breakpoint_threshold_type=\"interquartile\",\n",
    "    breakpoint_threshold_amount=0.5,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12e0d2d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# text_splitter를 사용하여 분할합니다.\n",
    "docs = text_splitter.create_documents([file])\n",
    "\n",
    "# 결과를 출력합니다.\n",
    "for i, doc in enumerate(docs[:5]):\n",
    "    print(f\"[Chunk {i}]\", end=\"\\n\\n\")\n",
    "    print(doc.page_content)  # 분할된 문서 중 첫 번째 문서의 내용을 출력합니다.\n",
    "    print(\"===\" * 20)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d186bb7",
   "metadata": {},
   "source": [
    "`docs`의 길이를 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c693c11",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(len(docs))  # docs의 길이를 출력합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9dec0348",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
